package com.atguigu.userprofile.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.userprofile.bean.TagCondition;
import com.atguigu.userprofile.bean.TagInfo;
import com.atguigu.userprofile.bean.UserGroup;
import com.atguigu.userprofile.constants.ConstCodes;
import com.atguigu.userprofile.mapper.UserGroupMapper;
import com.atguigu.userprofile.service.TagInfoService;
import com.atguigu.userprofile.service.UserGroupService;
import com.atguigu.userprofile.utils.RedisUtil;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;

import org.apache.catalina.User;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author zhangchen
 * @since 2021-05-04
 */
@Service
@Slf4j
@DS("mysql")
public class UserGroupServiceImpl extends ServiceImpl<UserGroupMapper, UserGroup> implements UserGroupService {

    @Autowired
    TagInfoService tagInfoService;

    @Override
    public void saveUserGroupInfo(UserGroup userGroup) {
        List<TagCondition> tagConditions = userGroup.getTagConditions();
        String conditionsJson = JSON.toJSONString(tagConditions);
        userGroup.setConditionJsonStr(conditionsJson);

        String conditionComment = userGroup.conditionJsonToComment();
        userGroup.setConditionComment(conditionComment);

        userGroup.setCreateTime(new Date());

        this.saveOrUpdate(userGroup);

    }

    @Override
    public void genUserGroupUs(UserGroup userGroup) {
        // 运行动态sql ，人群包写入ck
   //     String insertSQL= getInsertSQL(  userGroup);

        String insertSQL = getInsertSQL(userGroup);
        super.baseMapper.insertUserGroupCK(insertSQL);




    }


    // insert into user_group  select

    public String getInsertSQL(UserGroup userGroup){
        // 得到bitmapAndSQL
        String bitmapAndSQL = getBitmapAndSQL(userGroup.getTagConditions(), userGroup.getBusiDate());
        String insertSQL="insert into user_group select '"+userGroup.getId()+"',"+bitmapAndSQL;
        return insertSQL;

    }

    //bitmapAnd( bitmapAnd(
    //                             (select us from user_tag_value_string where tag_code='' and tag_value='' and dt=''
    //                              ),
    //							  (B)
    //					      )
    //					,
    //					(C)
    //				 )
    public  String getBitmapAndSQL(List<TagCondition> tagConditionList,String busiDate){
        //得到以一个子查询sql 用bitmap拼接可得
        // 查询一个标签map 作为缓存
        Map<String, TagInfo> tagInfoMapWithCode = tagInfoService.getTagInfoMapWithCode();
        StringBuilder stringBuilder=new StringBuilder();
        for (TagCondition tagCondition : tagConditionList) {
            String subQuerySQL = getSubQuerySQL(tagCondition, busiDate, tagInfoMapWithCode);
            if (stringBuilder.length()==0){
                stringBuilder.append("(").append(subQuerySQL).append(")");
            }else{
                stringBuilder.insert(0,"bitmapAnd(")
                        .append(",(").append(subQuerySQL).append("))");
            }

        }
        return stringBuilder.toString();
    }

    //select us from user_tag_value_string where tag_code='' and tag_value='' and dt=''
    //分析子查询 ：
    //1 表名： 得到标签的值类型  判断获得表名    标签值类型可以通过标签编码查询数据库得到
    //2 tag_code : 对象有
    //3 tag_value的比较符号 ：  通过operator 判断获得
    //4 tag_value :     1  单引问题  要通过标签值类型 进行 判断
    //                  2  当operator为 in 或 not in  时 要把多个标签值拼接，同时添加小括号
    //
    //5 dt : 对象中的busiDate
    private String getSubQuerySQL(TagCondition tagCondition,String busiDate,Map<String,TagInfo> tagInfoMap){
        //每个条件产生一个子查询
        //1 表名： 得到标签的值类型  判断获得表名    标签值类型可以通过标签编码查询数据库得到
        String tagCode = tagCondition.getTagCode();
        TagInfo tagInfo = tagInfoMap.get(tagCode);
        String tagValueType = tagInfo.getTagValueType();
        String tableName="";
        if(ConstCodes.TAG_VALUE_TYPE_STRING.equals(tagValueType)){
            tableName="user_tag_value_string";
        }else   if(ConstCodes.TAG_VALUE_TYPE_LONG.equals(tagValueType)){
            tableName="user_tag_value_long";
        }else  if(ConstCodes.TAG_VALUE_TYPE_DATE.equals(tagValueType)){
            tableName="user_tag_value_date";
        }else   if(ConstCodes.TAG_VALUE_TYPE_DECIMAL.equals(tagValueType)){
            tableName="user_tag_value_decimal";
        }

        //tag_value的比较符号 ：  通过operator 判断获得
        String conditionOperator = getConditionOperator(tagCondition.getOperator());
       //4 tag_value :     1  单引问题  要通过标签值类型 进行 判断
        //                  2  当operator为 in 或 not in  时 要把多个标签值拼接，同时添加小括号
        //
        String tagValue=null;
        if(ConstCodes.TAG_VALUE_TYPE_STRING.equals(tagValueType)||ConstCodes.TAG_VALUE_TYPE_DATE.equals(tagValueType)){
            //两大工具包 ：apache lang3   google guava
            tagValue="'"+ StringUtils.join(tagCondition.getTagValues(),"','")+"'";
        }else{
            tagValue=  StringUtils.join(tagCondition.getTagValues(),",") ;
        }
        if(tagCondition.getOperator().equals("in") ||tagCondition.getOperator().equals("nin") ){
            tagValue = "("+tagValue+")";
        }
        String dt=busiDate.replace("-","");
        //建议 StringBuilder拼接
        String sql="select groupBitmapMergeState(us) from "+tableName+" where" +
                " tag_code='" +tagCode.toLowerCase()+"' " +
                " and tag_value "+conditionOperator+" "+tagValue+ " " +
                " and dt='"+dt+"'";

        return sql;
    }


    // 把中文的操作代码转换为 判断符号
    private  String getConditionOperator(String operator){
        switch (operator){
            case "eq":
                return "=";
            case "lte":
                return "<=";
            case "gte":
                return ">=";
            case "lt":
                return "<";
            case "gt":
                return ">";
            case "neq":
                return "<>";
            case "in":
                return "in";
            case "nin":
                return "not in";
        }
        throw  new RuntimeException("操作符不正确");
    }



    @Override
    public void saveUserGroupRedis(UserGroup userGroup) {
        Jedis jedis = RedisUtil.getJedis();
        // type?  set      key? user_group:101   value? uid
        // field/score? 无   读api？ smembers 查全部 sismember 判存  写api? sadd
        // expire? 无

        //1 从ck 读取指定的分群人群包集合  存储在java list<String>
        // 1.1  因为考虑到ck的计算结果如果发生更新，可能会因为异步的问题的造成读取旧数据的情况
        // 所以改为重新计算一次，把计算结果写入redis
        String bitmapAndSQL = getBitmapAndSQL(userGroup.getTagConditions(), userGroup.getBusiDate());
        List<String> userGroupUsList = super.baseMapper.getUserGroupUsWithSQL(bitmapAndSQL);

      //   有异步问题
        //  List<String> userGroupUsList = super.baseMapper.getUserGroupUs(userGroup.getId().toString());

        //2  把list写入redis中
        String userGroupKey="user_group:"+userGroup.getId();
        //保证幂等性 写入之前先清理旧数据
        jedis.del(userGroupKey);

        String[] uids = userGroupUsList.toArray(new String[]{});
        jedis.sadd(userGroupKey,uids );

        //3 补充人数
        userGroup.setUserGroupNum(uids.length+0L);

        jedis.close();

    }

    @Override
    public Long evaluateUserGroupNum(UserGroup userGroup) {
        //    根据条件进行bitmapAnd 得到bitmap --> 对该bitmap 求基数  bitmapCardinality
        //  select bitmapCardinality(bitmapAnd(a,b)) ;

        String bitmapAndSQL = getBitmapAndSQL(userGroup.getTagConditions(), userGroup.getBusiDate());
        String bitmapCardSQL="select bitmapCardinality("+bitmapAndSQL+")";
        Long num = this.baseMapper.getBitmapCard(bitmapCardSQL);
        return num;
    }

    @Override
    public UserGroup refreshUserGroupUs(String userGroupId, String busiDate) {
        //根据userGroupId 查询出分群的基本信息 主要条件
        UserGroup userGroup = this.getById(userGroupId);

        String conditionJsonStr = userGroup.getConditionJsonStr();
        List<TagCondition> tagConditionList = JSON.parseArray(conditionJsonStr, TagCondition.class);
        userGroup.setTagConditions(tagConditionList);
        userGroup.setBusiDate(busiDate);
        // 删除原有数据
        delUserGroupUs(  userGroupId);
        //生成
        genUserGroupUs(userGroup);

        return userGroup;

    }

    private void   delUserGroupUs(String userGroupId){

            // alter table user_group delete where user_group_id = ''
        baseMapper.deleteUserGroup(userGroupId);



    }

}
